#include "IOUringEventLoop.h"
#include "Logger.h"
#include <string.h>
using namespace std::placeholders;

void handle_accept(IOUring *uring, int sockfd, InetAddress &peerAddr,
                   struct io_uring_cqe *cqe) {
    int connfd = cqe->res;
    if (connfd >= 0) {
        uring->addRead(connfd, uring->kMaxMessageLen, IOSQE_BUFFER_SELECT);
    }
    LOG_DEBUG("accept %s", peerAddr.toIpPort().c_str());
    uring->addAccept(sockfd, peerAddr, 0);
}

void handle_read(IOUring *uring, struct io_uring_cqe *cqe) {
    int bytesRead = cqe->res;
    int bid = cqe->flags >> 16;
    struct conn_info conni;
    memcpy(&conni, &cqe->user_data, sizeof(conni));

    if (cqe->res <= 0) {
        uring->addProvideBuf(bid);
        close(conni.fd_);
    } else {
        uring->addWrite(conni.fd_, bid, bytesRead, 0);
    }
}

void handle_write(IOUring *uring, struct io_uring_cqe *cqe) {
    struct conn_info conni;
    memcpy(&conni, &cqe->user_data, sizeof(conni));

    uring->addProvideBuf(conni.bid_);
    uring->addRead(conni.fd_, uring->kMaxMessageLen, IOSQE_BUFFER_SELECT);
}

int main() {

    int portno = 8000;
    struct sockaddr_in servAddr;
    // setup socket
    int sockListenFd = socket(AF_INET, SOCK_STREAM, 0);
    const int val = 1;
    setsockopt(sockListenFd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_port = htons(portno);
    servAddr.sin_addr.s_addr = INADDR_ANY;

    // bind and listen
    if (bind(sockListenFd, (struct sockaddr *)&servAddr, sizeof(servAddr)) <
        0) {
        perror("Error binding socket...\n");
        exit(1);
    }
    if (listen(sockListenFd, 512) < 0) {
        perror("Error listening on socket...\n");
        exit(1);
    }
    LOG_DEBUG("io_uring echo server listening for connections on port: %d",
              portno);

    IOUringEventLoop loop(nullptr);

    loop.addProvideBufAll();

    InetAddress peerAddr;
    loop.setNewConnectionCallback(
        std::bind(handle_accept, loop.ioUring(), sockListenFd, peerAddr, _1));
    loop.setReadCompleteCallback(std::bind(handle_read, loop.ioUring(), _1));
    loop.setWriteCompleteCallback(std::bind(handle_write, loop.ioUring(), _1));

    loop.addAccept(sockListenFd, peerAddr, 0);
    LOG_DEBUG("start loop");
    loop.loop();
}
